/**
 * Copyright © 2013 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.rayson.share.transport;

import java.util.Arrays;

/**
 * An buffer used to write byte array content.
 * 
 * @author creativor
 */
public class ByteArrayBuffer {

	private static final int INTIALIZATION_CAPACITY = 512;

	/**
	 * The buffer where data is stored.
	 */
	private byte buf[];

	/**
	 * The number of valid bytes in the buffer.
	 */
	private int count;

	/**
	 * Creates a new byte array buffer. The buffer capacity is initially 32
	 * bytes, though its size increases if necessary.
	 */
	public ByteArrayBuffer() {
		this(INTIALIZATION_CAPACITY);
	}

	/**
	 * Creates a new byte array buffer, with a buffer capacity of the specified
	 * size, in bytes.
	 * 
	 * @param size
	 *            the initial size.
	 * @exception IllegalArgumentException
	 *                if size is negative.
	 */
	public ByteArrayBuffer(final int size) {
		if (size < 0) {
			throw new IllegalArgumentException("Negative initial size: " + size);
		}
		buf = new byte[size];
	}

	/**
	 * Increases the capacity if necessary to ensure that it can hold at least
	 * the number of elements specified by the minimum capacity argument.
	 * 
	 * @param minCapacity
	 *            the desired minimum capacity
	 * @throws OutOfMemoryError
	 *             if {@code minCapacity < 0}. This is interpreted as a request
	 *             for the unsatisfiably large capacity
	 *             {@code (long) Integer.MAX_VALUE + (minCapacity - Integer.MAX_VALUE)}
	 *             .
	 */
	private void ensureCapacity(final int minCapacity) {
		// overflow-conscious code
		if (minCapacity - buf.length > 0)
			grow(minCapacity);
	}

	/**
	 * Increases the capacity to ensure that it can hold at least the number of
	 * elements specified by the minimum capacity argument.
	 * 
	 * @param minCapacity
	 *            the desired minimum capacity
	 */
	private void grow(final int minCapacity) {
		// overflow-conscious code
		final int oldCapacity = buf.length;
		int newCapacity = oldCapacity << 1;
		if (newCapacity - minCapacity < 0)
			newCapacity = minCapacity;
		if (newCapacity < 0) {
			if (minCapacity < 0) // overflow
				throw new OutOfMemoryError();
			newCapacity = Integer.MAX_VALUE;
		}
		buf = Arrays.copyOf(buf, newCapacity);
	}

	/**
	 * Resets the <code>count</code> field of this byte array output stream to
	 * zero, so that all currently accumulated output in the buffer is
	 * discarded. The buffer can be used again, reusing the already allocated
	 * buffer space.
	 * 
	 * @see java.io.ByteArrayInputStream#count
	 */
	public void reset() {
		count = 0;
	}

	/**
	 * Returns the current size of the buffer.
	 * 
	 * @return the value of the <code>count</code> field, which is the number of
	 *         valid bytes in this buffer.
	 * @see java.io.ByteArrayOutputStream#count
	 */
	public int size() {
		return count;
	}

	/**
	 * Creates a newly allocated byte array. Its size is the current size of
	 * this buffer and the valid contents of the buffer have been copied into
	 * it.
	 * 
	 * @return the current contents of this buffer, as a byte array.
	 * @see java.io.ByteArrayOutputStream#size()
	 */
	public byte toByteArray()[] {
		return Arrays.copyOf(buf, count);
	}

	/**
	 * Converts the buffer's contents into a string decoding bytes using the
	 * platform's default character set. The length of the new <tt>String</tt>
	 * is a function of the character set, and hence may not be equal to the
	 * size of the buffer.
	 * 
	 * <p>
	 * This method always replaces malformed-input and unmappable-character
	 * sequences with the default replacement string for the platform's default
	 * character set. The {@linkplain java.nio.charset.CharsetDecoder} class
	 * should be used when more control over the decoding process is required.
	 * 
	 * @return String decoded from the buffer's contents.
	 * @since JDK1.1
	 */
	@Override
	public String toString() {
		return new String(buf, 0, count);
	}

	/**
	 * Writes the specified byte array to this byte array buffer.
	 * 
	 * @param b
	 *            the data.
	 */
	public void write(final byte b[]) {
		this.write(b, 0, b.length);
	}

	/**
	 * Writes <code>len</code> bytes from the specified byte array starting at
	 * offset <code>off</code> to this byte array buffer.
	 * 
	 * @param b
	 *            the data.
	 * @param off
	 *            the start offset in the data.
	 * @param len
	 *            the number of bytes to write.
	 */
	public void write(final byte b[], final int off, final int len) {
		if ((off < 0) || (off > b.length) || (len < 0)
				|| ((off + len) - b.length > 0)) {
			throw new IndexOutOfBoundsException();
		}
		ensureCapacity(count + len);
		System.arraycopy(b, off, buf, count, len);
		count += len;
	}

	/**
	 * Writes the specified byte to this byte array buffer.
	 * 
	 * @param b
	 *            the byte to be written.
	 */
	public void write(final int b) {
		ensureCapacity(count + 1);
		buf[count] = (byte) b;
		count += 1;
	}

}